測試是確保產品品質關鍵步驟之一。為了確保系統在任何情況下都能正確運行,開發人員會撰寫測試案例來驗證功能與性能。在這個過程中,Mocking 是一個不可或缺的方法。接下來要探討一下 Mocking 的核心概念及在測試中的重要性。
減少測試的複雜性+隔離單元測試的範疇+避免對外部服務或資料庫的真實依賴
例如,當我們測試 Service Layer 時,它可能依賴於資料庫、網路等外部資源。Mocking 允許我們創建這些外部資源的 mock 版本,從而減少測試的複雜性和不確定性,確保測試專注在被測模組的邏輯,而不是其依賴的具體實現。此外,依賴於真實環境的測試通常不易重複,因為外部環境可能隨時變化。Mocking 方便我們模擬外部依賴的行為,從而克服這些問題。
Mocking
如剛剛所述,Mocking 是模擬模組間互動的過程。當我們說 mock
一個物件時,意味著在創建一個真實物件「假的」或「虛擬」的版本,並控制其行為以符合測試需求。
Stubbing
Stubbing 是 Mocking 的一種。當 stub
一個方法時,我們提供一個預先定義好的回應來模擬真實行為。Stubbing 不關心方法被呼叫的次數或其參數,它只是在被呼叫時回傳一個固定的結果。
Spying
Spying 則與 Mocking 類似,但當 spy
一個物件時,只模擬部分方法的行為,而其他方法則保持原狀。Spying 常用在想檢查或驗證物件的部分行為,同時也想保留其他方法真實行為。
Mocking、Stubbing、和 Spying 是單元測試時常用來模擬和控制物件行為的三種手段。
在 Mockito 中,創建一個 Mock 物件非常直覺。假設有一個 PaymentService
並在測試裡模擬它的行為:
PaymentService paymentServiceMock = Mockito.mock(PaymentService.class);
當 Mock 物件建立後可以去定義它的行為。例如,可以指定當呼叫 paymentServiceMock.processPayment(any())
時,return 特定值或觸發特定行為:
Mockito.when(paymentServiceMock.processPayment(any(Payment.class))).thenReturn(true);
any(Payment.class)
是一個 Argument Matcher。
Mockito.verify(paymentServiceMock).processPayment(any(Payment.class));
Argument Matchers 是在模擬方法行為時,對輸入參數的預期值進行彈性的定義。例如:
Mockito.when(paymentServiceMock.processPayment(any(Payment.class))).thenReturn(true);
在這裡 any(Payment.class)
是一個 Argument Matcher,表示我們不關心實際傳入 processPayment
方法的具體 Payment
物件是什麼。
Mockito 提供一系列的註解:
@RunWith(MockitoJUnitRunner.class)
public class PaymentServiceTest {
@Mock
PaymentService paymentServiceMock;
@InjectMocks
OrderService orderService;
// ... 其他測試
}
@Mock
註解是用來建立一個PaymentService
的 Mock 物件,而@InjectMocks
則將所有的 Mock 物件自動注入到OrderService
中。
可以模擬方法呼叫後的返回值,也可以模擬方法被呼叫時時拋出 exception:
Mockito.when(paymentServiceMock.processPayment(any(Payment.class))).thenThrow(new PaymentException());